// @Author EthanScriptOn
// @Desc
package starter

import (
	"errors"
	"fmt"
	"gitee.com/fatzeng/srf_switch_log/log"
	"gitee.com/fatzeng/srf_switch_log/log_context"
	"gitee.com/fatzeng/srf_switch_log/log_define"
	"math"
)

func Start(ctx *log_context.LogInitializationContext) (err error) {
	if ctx == nil {
		return errors.New("log initialization config_context cannot be empty")
	}
	logProfiles, err := loadRules(ctx)
	if err != nil {
		return
	}
	if len(logProfiles) == 0 {
		return errors.New("log profiles is empty")
	}
	conversionCenter, err := log.GetLogCenter()
	if err != nil {
		return err
	}
	return conversionCenter.GenerateRuleBinding(logProfiles)
}

func loadRules(ctx *log_context.LogInitializationContext) (logProfiles []*log_define.LogProfile, err error) {
	profiles, err := ctx.GetLogProfiles()
	if err != nil {
		return nil, err
	}
	targetLogProfiles := make([]*log_define.LogProfile, 0)
	for _, profilesGroup := range mergeSimilarCategories(profiles) {
		profilesGroupTemp := profilesGroup
		if targetLogProfile, err := mergeFilteringRules(profilesGroupTemp); err != nil {
			return nil, err
		} else {
			targetLogProfiles = append(targetLogProfiles, targetLogProfile)
		}
	}
	return derivative(targetLogProfiles), nil
}

// derivative Derived, multiple rules will be derived depending on the level
func derivative(targetLogProfiles []*log_define.LogProfile) []*log_define.LogProfile {
	finalLogProfiles := make([]*log_define.LogProfile, 0)
	for _, profile := range targetLogProfiles {
		profileTemp := profile
		if profile.Level >= len(log_define.PriorityLogLevel)-1 {
			finalLogProfiles = append(finalLogProfiles, profileTemp)
			continue
		}
		derivativeLevels := log_define.PriorityLogLevel[profile.Level+1 : len(log_define.PriorityLogLevel)]
		for _, level := range derivativeLevels {
			deepCopyLogProfile := profileTemp.DeepCopy()
			deepCopyLogProfile.Level = int(level)
			finalLogProfiles = append(finalLogProfiles, deepCopyLogProfile)
		}
	}
	return finalLogProfiles
}

// MergedUnit A temporary structure for advanced processing
type MergedUnit struct {
	destination      *log_define.LogOutputDestination
	logProfilesGroup []*log_define.LogProfile
}

// mergeSimilarCategories Merge based on key, and the result of the merge may contain duplicates
func mergeSimilarCategories(profiles []*log_define.LogProfile) map[string]*MergedUnit {
	mergedLogProfiles := make(map[string]*MergedUnit)
	for _, profile := range profiles {
		for _, destination := range profile.Destinations {
			// The latitude of the key Uniqueness can be determined
			key := fmt.Sprintf("%s-%s-%s-%s-%s", profile.Level, profile.Template, destination.Type, destination.Path, destination.FileNamePattern)
			logProfilesGroup, ok := mergedLogProfiles[key]
			if !ok {
				logProfilesGroup = new(MergedUnit)
			}
			profileTemp := profile
			destinationTemp := destination
			logProfilesGroup.destination = destinationTemp
			logProfilesGroup.logProfilesGroup = append(logProfilesGroup.logProfilesGroup, profileTemp)
		}
	}
	return mergedLogProfiles
}

// mergeFilteringRules Group-level merging, this step will filter out the most appropriate items
func mergeFilteringRules(profilesGroup *MergedUnit) (*log_define.LogProfile, error) {
	logProfile := new(log_define.LogProfile)
	// The group to be compared for subsequent purposes
	compareOption := foundTargetProfiles(profilesGroup.destination, profilesGroup.logProfilesGroup, func(profile *log_define.LogProfile) {
		logProfile.Filters = append(logProfile.Filters, profile.Filters...)
	})
	// Probably console type output
	if len(compareOption) <= 0 && len(profilesGroup.logProfilesGroup) != 0 {
		logProfileTemp := profilesGroup.logProfilesGroup[0]
		buildLogProfile(logProfileTemp, logProfile)
		return logProfile, nil
	}
	// Choose the most suitable one according to the comparison rules
	minMaxFileSize := math.MaxInt
	targetProfiles := make([]*log_define.LogProfile, 0)
	for destination, profile := range compareOption {
		profileTemp := profile
		if destination.MaxFileSize < minMaxFileSize {
			minMaxFileSize = destination.MaxFileSize
			// reset
			targetProfiles = []*log_define.LogProfile{profileTemp}
		} else if destination.MaxFileSize == minMaxFileSize {
			targetProfiles = append(targetProfiles, profileTemp)
		}
	}
	// There is only one direct return
	if len(targetProfiles) == 1 {
		logProfileTemp := targetProfiles[0]
		buildLogProfile(logProfileTemp, logProfile)
		return logProfile, nil
	}

	// A second round of comparisons was conducted
	if len(targetProfiles) > 1 {
		minValidityRotation := log_define.YEAR
		compareMin := func(i, j log_define.LogSnapshotDateBit) (bool, error) {

			// If the enumeration constant is not in the map, an error is returned
			if int(i) <= 0 || int(i) > len(log_define.PriorityLogSnapshotDateBit) || int(j) <= 0 || int(j) > len(log_define.PriorityLogSnapshotDateBit) {
				return false, fmt.Errorf("unclear priority: [%v] [%v]", i, j)
			}

			if int(i) < int(j) {
				return true, nil
			}

			return false, nil
		}

		for destination, profile := range foundTargetProfiles(profilesGroup.destination, targetProfiles, nil) {
			profileTemp := profile
			if isMin, err := compareMin(log_define.LogSnapshotDateBit(destination.ValidityRotation), minValidityRotation); err != nil {
				return nil, err
			} else {
				if isMin {
					minValidityRotation = log_define.LogSnapshotDateBit(destination.ValidityRotation)
					// reset
					targetProfiles = []*log_define.LogProfile{profileTemp}
				} else if log_define.LogSnapshotDateBit(destination.ValidityRotation) == minValidityRotation {
					targetProfiles = append(targetProfiles, profileTemp)
				}
			}
		}
		if len(targetProfiles) == 1 {
			logProfileTemp := targetProfiles[0]
			buildLogProfile(logProfileTemp, logProfile)
			return logProfile, nil
		}

		// The third round of comparisons
		if len(targetProfiles) > 1 {
			minMaxFiles := math.MaxInt
			for destination, profile := range foundTargetProfiles(profilesGroup.destination, targetProfiles, nil) {
				profileTemp := profile
				if destination.MaxFiles < minMaxFiles {
					minMaxFiles = destination.MaxFiles
					// reset
					targetProfiles = []*log_define.LogProfile{profileTemp}
				} else if destination.MaxFiles == minMaxFiles {
					targetProfiles = append(targetProfiles, profileTemp)
				}
			}

			if len(targetProfiles) == 1 {
				logProfileTemp := targetProfiles[0]
				buildLogProfile(logProfileTemp, logProfile)
				return logProfile, nil
			}
			// No more comparisons, just go back
			if len(targetProfiles) > 1 {
				return targetProfiles[0], nil
			}
		}
	}
	return nil, errors.New("can not found any log profile")
}

// foundTargetProfiles Search for a LogProfile group based on the target
func foundTargetProfiles(rule *log_define.LogOutputDestination, compareLogProfiles []*log_define.LogProfile, filtersDo func(*log_define.LogProfile)) map[*log_define.LogOutputDestination]*log_define.LogProfile {
	compareOption := make(map[*log_define.LogOutputDestination]*log_define.LogProfile)
	for _, profile := range compareLogProfiles {
		if filtersDo != nil {
			filtersDo(profile)
		}
		for _, destination := range profile.Destinations {
			// Selected from the group according to the target
			if rule.Type != destination.Type ||
				rule.Path != destination.Path ||
				rule.FileNamePattern != destination.FileNamePattern {
				continue
			}
			destinationTemp := destination
			profileTemp := profile
			compareOption[destinationTemp] = profileTemp
		}
	}
	return compareOption
}

// buildLogProfile Building a LogProfile where filters are added elsewhere
func buildLogProfile(src, target *log_define.LogProfile) {
	target.Template = src.Template
	target.Level = src.Level
	targetLogOutputDestination := make([]*log_define.LogOutputDestination, 0)
	for _, destination := range src.Destinations {
		destinationTemp := destination
		targetLogOutputDestination = append(targetLogOutputDestination, destinationTemp.DeepCopy())
	}
	target.Destinations = targetLogOutputDestination
}
